<?php
namespace App\Services\Tool\Pay;
use Log;

class UnionPayTool{
	private static $signCerts = array();
    private static $encryptCerts = array();
    private static $verifyCerts = array();
	/**
	 * 代付
	 */
	function transfer_accounts($input){
        $input['customerNm']  = '张三';//／测试
		$arr['version']       = '5.0.0';
		$arr['encoding']      = 'UTF-8';
		$arr['certId']        = '';//证书ID
		$arr['encryptCertId'] = $this->getEncryptCertId(storage_path(env('SDK_ENCRYPT_CERT_PATH')));//加密证书ID
		$arr['signMethod']    = '01';//签名方法
		$arr['signature']     = '';//签名
		$arr['txnType']       = 12;//交易类型
		$arr['txnSubType']    = '00';//交易子类
		$arr['bizType']       = '000401';
		$arr['channelType']   = '07';
		$arr['accessType']    = 0;
		$arr['merId']         = env('UNION_MERID');//商户代码
		$arr['backUrl']       = env('HOME_URL').env('UNION_BACKURL');
		$arr['orderId']       = $input['order_id'];
		$arr['currencyCode']  = '156';
		$arr['txnAmt']        = $input['total_fee'] * 100;//交易金额 : 单位为分，不能带小数点
		$arr['txnTime']       = date('YmdHis',time());//订单发送时间 必须使用当前北京时间（年年年年月月日日时时分分秒秒）24小时制，样例：20151123152540，北京时间
		$arr['accNo']         = '6226090000000048';$input['accNo'];//银行卡号 6226090000000048测试
		$arr['accNo']		  = $this->encryptData($arr['accNo'],storage_path(env('SDK_ENCRYPT_CERT_PATH')));
		$arr['customerInfo']  = ['customerNm' => $input['customerNm']];
		$arr['customerInfo']  = $this->getCustomerInfoWithEncrypt($arr['customerInfo']);

		$new 				  = $this->sign($arr,$this->get_SDK_SIGN_CERT_PATH(),$this->get_SDK_SIGN_CERT_PWD());

		$result 			  = $this->post($new,env('SDK_BACK_TRANS_URL'));
		return $result;
	}

	/**
	 	工具
	 */

	 /**
	 * 后台交易 HttpClient通信
	 *
	 * @param unknown_type $params
	 * @param unknown_type $url
	 * @return mixed
	 */
	 function post($params, $url) {

		 $opts = $this->createLinkString ( $params, false, true );
		 Log::info( "后台请求地址为>" . $url );
		 Log::info( "后台请求报文为>" . $opts );

		 $ch = curl_init ();
		 curl_setopt ( $ch, CURLOPT_URL, $url );
		 curl_setopt ( $ch, CURLOPT_POST, 1 );
		 curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, false ); // 不验证证书
		 curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, false ); // 不验证HOST
		 curl_setopt ( $ch, CURLOPT_SSLVERSION, 1 ); // http://php.net/manual/en/function.curl-setopt.php页面搜CURL_SSLVERSION_TLSv1
		 curl_setopt ( $ch, CURLOPT_HTTPHEADER, array (
				'Content-type:application/x-www-form-urlencoded;charset=UTF-8'
		 ) );
		 curl_setopt ( $ch, CURLOPT_POSTFIELDS, $opts );
		 curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
		 $html = curl_exec ( $ch );
		 Log::info( "后台返回结果为>" . $html );

		 if(curl_errno($ch)){
			 $errmsg = curl_error($ch);
			 curl_close ( $ch );
			 Log::info( "请求失败，报错信息>" . $errmsg );
			 return null;
		 }
	     if( curl_getinfo($ch, CURLINFO_HTTP_CODE) != "200"){
			$errmsg = "http状态=" . curl_getinfo($ch, CURLINFO_HTTP_CODE);
			curl_close ( $ch );
			Log::info( "请求失败，报错信息>" . $errmsg );
			return null;
	    }
		curl_close ( $ch );
		$result_arr = $this->convertStringToArray ( $html );
		return $result_arr;
	}

	/**
	 * 签名
	 * @param req 请求要素
	 * @param resp 应答要素
	 * @return 是否成功
	 */
	function sign($params, $cert_path=SDK_SIGN_CERT_PATH, $cert_pwd=SDK_SIGN_CERT_PWD) {

		$params ['certId'] = $this->getSignCertIdFromPfx($cert_path, $cert_pwd); //证书ID

		Log::info( '=====签名报文开始======' );
		if(isset($params['signature'])){
			unset($params['signature']);
		}
		// 转换成key=val&串
		$params_str = $this->createLinkString ( $params, true, false );
		Log::info( "签名key=val&...串 >" . $params_str );

		$params_sha1x16 = sha1 ( $params_str, FALSE );
		Log::info( "摘要sha1x16 >" . $params_sha1x16 );

		$private_key = $this->getSignKeyFromPfx( $cert_path, $cert_pwd );

		// 签名
		$sign_falg = openssl_sign ( $params_sha1x16, $signature, $private_key, OPENSSL_ALGO_SHA1 );
		if ($sign_falg) {
			$signature_base64 = base64_encode ( $signature );
			Log::info( "签名串为 >" . $signature_base64 );
			$params ['signature'] = $signature_base64;
		} else {
			Log::info( ">>>>>签名失败<<<<<<<" );
		}
		Log::info( '=====签名报文结束======' );
		return $params;
	}

    /**
     * 验签
     * @param $params 应答数组
     * @return 是否成功
     */
    function validate($params) {
        // 公钥
        $public_key = $this->getVerifyCertByCertId ( $params ['certId'] );
//  echo $public_key.'<br/>';
        // 签名串
        $signature_str = $params ['signature'];
        unset ( $params ['signature'] );
        $params_str = $this->createLinkString ( $params, true, false );
        Log::info( '报文去[signature] key=val&串>' . $params_str );
        $signature = base64_decode ( $signature_str );
//  echo date('Y-m-d',time());
        $params_sha1x16 = sha1 ( $params_str, FALSE );
        Log::info( '摘要shax16>' . $params_sha1x16 );
        $isSuccess = openssl_verify ( $params_sha1x16, $signature,$public_key, OPENSSL_ALGO_SHA1 );
        Log::info( $isSuccess ? '验签成功' : '验签失败' );
        return $isSuccess;
    }

    function getVerifyCertByCertId($certId){

        if(count(self::$verifyCerts) == 0){
            self::initVerifyCerts(storage_path(env('SDK_VERIFY_CERT_DIR')));
        }
        if(count(self::$verifyCerts) == 0){
            Log::info("未读取到任何证书……");
            return null;
        }
        if(array_key_exists($certId, self::$verifyCerts)){
            return self::$verifyCerts[$certId]->key;
        } else {
            Log::info("未匹配到序列号为[" . certId . "]的证书");
            return null;
        }
    }

    function initVerifyCerts($cert_dir=SDK_VERIFY_CERT_DIR) {
        Log::info( '验证签名证书目录 :>' . $cert_dir );
        $handle = opendir ( $cert_dir );
        if (!$handle) {
            Log::info( '证书目录 ' . $cert_dir . '不正确' );
            return;
        }
        while ($file = readdir($handle)) {
            clearstatcache();
            $filePath = $cert_dir . '/' . $file;
            if (is_file($filePath)) {
                if (pathinfo($file, PATHINFO_EXTENSION) == 'cer') {
                    $x509data = file_get_contents($filePath);
                    if($x509data === false ){
                        Log::info($filePath . "读取失败。");
                    }
                    $cert = new Cert();
                    openssl_x509_read($x509data);
                    $certdata = openssl_x509_parse($x509data);
                    $cert->certId = $certdata ['serialNumber'];

//                     $certId = CertSerialUtil::getSerial($x509data, $errMsg);
//                     if($certId === false){
//                      $logger->LogInfo("签名证书读取序列号失败：" . $errMsg);
//                      return;
//                     }
//                     $cert->certId = $certId;

                    $cert->key = $x509data;
                    self::$verifyCerts[$cert->certId] = $cert;
                    Log::info($filePath . "读取成功，序列号：" . $cert->certId);
                }
            }
        }
        closedir ( $handle );
    }

	/**
	 	获取证书id
	 */
	function getSignCertIdFromPfx($certPath=SDK_SIGN_CERT_PATH, $certPwd=SDK_SIGN_CERT_PWD)
    {
        if (!array_key_exists($certPath, self::$signCerts)) {
            $this->initSignCert($certPath, $certPwd);
        }
        return self::$signCerts[$certPath] -> certId;
    }

    function getSignKeyFromPfx($certPath=SDK_SIGN_CERT_PATH, $certPwd=SDK_SIGN_CERT_PWD)
    {
        if (!array_key_exists($certPath, self::$signCerts)) {
            $this->initSignCert($certPath, $certPwd);
        }
        return self::$signCerts[$certPath] -> key;
    }

    /**
     	获取加密证书id
     */
    function getEncryptCertId($cert_path=SDK_ENCRYPT_CERT_PATH){
        if(!array_key_exists($cert_path, self::$encryptCerts)){
            self::initEncryptCert($cert_path);
        }
        return self::$encryptCerts[$cert_path] -> certId;
    }
    private static function initEncryptCert($cert_path)
    {
        Log::info("读取加密证书……");
        $cert = new Cert();
	    $x509data = file_get_contents ( $cert_path );
        if($x509data === false ){
        	Log::info($cert_path . "读取失败。");
        }

	    openssl_x509_read ( $x509data );
	    $certdata = openssl_x509_parse ( $x509data );
	    $cert->certId = $certdata ['serialNumber'];

// 	    $certId = CertSerialUtil::getSerial($x509data, $errMsg);
// 	    if($certId === false){
// 	    	$logger->LogInfo("签名证书读取序列号失败：" . $errMsg);
// 	    	return;
// 	    }
// 	    $cert->certId = $certId;

        $cert->key = $x509data;
        self::$encryptCerts[$cert_path] = $cert;
        Log::info("加密证书读取成功，序列号：" . $cert->certId);
    }

    function initSignCert($certPath, $certPwd){
        Log::info("读取签名证书……");
        $cert = new Cert();

        $pkcs12certdata = file_get_contents ( $certPath );
        if($pkcs12certdata === false ){
        	Log::info($certPath . "读取失败。");
        }

        openssl_pkcs12_read ( $pkcs12certdata, $certs, $certPwd );
        $x509data = $certs ['cert'];

        openssl_x509_read ( $x509data );
        $certdata = openssl_x509_parse ( $x509data );
        $cert->certId = $certdata ['serialNumber'];

// 		$certId = CertSerialUtil::getSerial($x509data, $errMsg);
// 		if($certId === false){
//         	$logger->LogInfo("签名证书读取序列号失败：" . $errMsg);
//         	return;
// 		}
//         $cert->certId = $certId;

        $cert->key = $certs ['pkey'];
        $cert->cert = $x509data;

        Log::info("签名证书读取成功，序列号：" . $cert->certId);
        self::$signCerts[$certPath] = $cert;
    }

    //证书地址
    static function get_SDK_SIGN_CERT_PATH(){
    	return storage_path(env('SDK_SIGN_CERT_PATH'));
    }
    //证书密码
    static function get_SDK_SIGN_CERT_PWD(){
    	return env('SDK_SIGN_CERT_PWD');
    }

    /**
	 * map转换string，按新规范加密
	 *
	 * @param
	 *        	$customerInfo
	 */
    function getCustomerInfoWithEncrypt($customerInfo) {
	  if($customerInfo == null || count($customerInfo) == 0 )
	  	return "";
		$encryptedInfo = array();
		foreach ( $customerInfo as $key => $value ) {
			if ($key == 'phoneNo' || $key == 'cvn2' || $key == 'expired' ) {
			//if ($key == 'phoneNo' || $key == 'cvn2' || $key == 'expired' || $key == 'certifTp' || $key == 'certifId') {
				$encryptedInfo [$key] = $customerInfo [$key];
				unset ( $customerInfo [$key] );
			}
		}
		if( count ($encryptedInfo) > 0 ){
			$encryptedInfo = $this->createLinkString ( $encryptedInfo, false, false );
			$encryptedInfo = $this->encryptData ( $encryptedInfo, storage_path(env('SDK_ENCRYPT_CERT_PATH')) );
			$customerInfo ['encryptedInfo'] = $encryptedInfo;
		}
		return base64_encode ( "{" . $this->createLinkString ( $customerInfo, false, false ) . "}" );
	}

	function getEncryptKey($cert_path=SDK_ENCRYPT_CERT_PATH){
        if(!array_key_exists($cert_path, self::$encryptCerts)){
            $this->initEncryptCert($cert_path);
        }
        return self::$encryptCerts[$cert_path] -> key;
    }

	/**
	 * 加密数据
	 * @param string $data数据
	 * @param string $cert_path 证书配置路径
	 * @return unknown
	 */
	function encryptData($data, $cert_path=SDK_ENCRYPT_CERT_PATH) {
		$public_key = $this->getEncryptKey( $cert_path );
		openssl_public_encrypt ( $data, $crypted, $public_key );
		return base64_encode ( $crypted );
	}

	/**
	 * 解密数据
	 * @param string $data数据
	 * @param string $cert_path 证书配置路径
	 * @return unknown
	 */
	function decryptData($data, $cert_path=SDK_SIGN_CERT_PATH, $cert_pwd=SDK_SIGN_CERT_PWD) {
		$data = base64_decode ( $data );
		$private_key = $this->getSignKeyFromPfx ( $cert_path, $cert_pwd);
		openssl_private_decrypt ( $data, $crypted, $private_key );
		return $crypted;
	}

    /**
	 * 讲数组转换为string
	 *
	 * @param $para 数组
	 * @param $sort 是否需要排序
	 * @param $encode 是否需要URL编码
	 * @return string
	 */
	function createLinkString($para, $sort, $encode) {
		if($para == NULL || !is_array($para))
			return "";

		$linkString = "";
		if ($sort) {
			$para = $this->argSort ( $para );
		}
		while ( list ( $key, $value ) = each ( $para ) ) {
			if ($encode) {
				$value = urlencode ( $value );
			}
			$linkString .= $key . "=" . $value . "&";
		}
		// 去掉最后一个&字符
		$linkString = substr ( $linkString, 0, count ( $linkString ) - 2 );

		return $linkString;
	}
	/**
	 * 对数组排序
	 *
	 * @param $para 排序前的数组
	 *        	return 排序后的数组
	 */
	function argSort($para) {
		ksort ( $para );
		reset ( $para );
		return $para;
	}

	/**
	 * key1=value1&key2=value2转array
	 * @param $str key1=value1&key2=value2的字符串
	 * @param $$needUrlDecode 是否需要解url编码，默认不需要
	 */
	function parseQString($str, $needUrlDecode=false){
		$result = array();
		$len = strlen($str);
		$temp = "";
		$curChar = "";
		$key = "";
		$isKey = true;
		$isOpen = false;
		$openName = "\0";

		for($i=0; $i<$len; $i++){
			$curChar = $str[$i];
			if($isOpen){
				if( $curChar == $openName){
					$isOpen = false;
				}
				$temp = $temp . $curChar;
			} elseif ($curChar == "{"){
				$isOpen = true;
				$openName = "}";
				$temp = $temp . $curChar;
			} elseif ($curChar == "["){
				$isOpen = true;
				$openName = "]";
				$temp = $temp . $curChar;
			} elseif ($isKey && $curChar == "="){
				$key = $temp;
				$temp = "";
				$isKey = false;
			} elseif ( $curChar == "&" && !$isOpen){
				$this->putKeyValueToDictionary($temp, $isKey, $key, $result, $needUrlDecode);
				$temp = "";
				$isKey = true;
			} else {
				$temp = $temp . $curChar;
			}
		}
		$this->putKeyValueToDictionary($temp, $isKey, $key, $result, $needUrlDecode);
		return $result;
	}


	function putKeyValueToDictionary($temp, $isKey, $key, &$result, $needUrlDecode) {
		if ($isKey) {
			$key = $temp;
			if (strlen ( $key ) == 0) {
				return false;
			}
			$result [$key] = "";
		} else {
			if (strlen ( $key ) == 0) {
				return false;
			}
			if ($needUrlDecode)
				$result [$key] = urldecode ( $temp );
			else
				$result [$key] = $temp;
		}
	}

	/**
	 * 字符串转换为 数组
	 *
	 * @param unknown_type $str
	 * @return multitype:unknown
	 */
	function convertStringToArray($str) {
		return $this->parseQString($str);
	}

}
class Cert
{
    public $cert;
    public $certId;
    public $key;
}
